package com.jj.service.user;

import com.jj.aop.annotation.RPC;
import com.jj.battle.protocol.GameProto;
import com.jj.config.Constants;
import com.jj.core.client.Client;
import com.jj.core.client.impl.ClientManagerImpl;
import com.jj.core.rpc.RPCHandler;
import com.jj.dao.entity.User;
import com.jj.dao.repository.UserRepository;
import com.jj.message.Result;
import com.jj.message.battle.UserMessage;
import com.jj.net.kcp.KcpChannelManager;
import com.jj.net.tcp.TcpChannelListener;
import com.jj.protocol.LogonResponse;
import com.jj.protocol.RoomInfo;
import com.jj.service.ConvService;
import com.jj.service.player.Player;
import com.jj.service.player.PlayerManagerImpl;
import com.jj.service.room.Room;
import com.jj.service.room.RoomManagerImpl;
import com.jj.thrift.client.VerifyClient;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * Copyrights: QidianGroup@JJ
 *
 * @author: LuHan
 * @date: 2018/1/15, 14:04
 */
@Component
public class UserService extends RPCHandler{
    private static Logger logger = getLogger(UserService.class);

    @Autowired
    private ClientManagerImpl clientManager;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ConvService convService;

    @Autowired
    private RoomManagerImpl roomManager;

    @Autowired
    private PlayerManagerImpl playerManager;

    @Autowired
    private KcpChannelManager kcpChannelManager;

    @Autowired
    private TcpChannelListener tcpChannelListener;

    @Autowired
    private VerifyClient verifyClient;

    @RPC(id = 1, args = {"guid","firstLogon"})
    public UserMessage onLogon(String guid, Boolean firstLogon){
        logger.debug("rcv guid:{} firstLogon:{}", guid, firstLogon);
        Result<LogonResponse> result = new Result<>();

        User user = userRepository.findOneByGuid(guid);
        if(user == null){
            user = createNewUser(guid);
            saveUser(user);
            user = userRepository.findOneByGuid(guid);
        }

        if(firstLogon){
            logger.debug("first logon for {}, reset", guid);
            //reset KCP state
            kcpChannelManager.resetKCP(user.getUid());
        }

        LogonResponse response = new LogonResponse();
        response.setUid(user.getUid());
        response.setChanllengeTxt(UUID.randomUUID().toString());

        response.setKcpState(convService.getKcpState(user.getUid()));

        result.setCode(0);
        result.setValue(response);

        //bind
        getContext().setAttr(Constants.USER_ID_KEY, user.getUid());
        tcpChannelListener.bind(user.getUid(), getContext());
        Client client = clientManager.get(user.getUid());
        client.setTcpContext(getContext());

        UserMessage userMessage = clientManager.msg().onReturn(result);
        return userMessage;
    }

    @RPC(id = 3)
    public UserMessage onQueryCurrentState(){
        logger.debug("onQueryCurrentState");
        Result<Integer> result = new Result<>();
        result.setCode(0);
        result.setValue(0);
        return clientManager.msg().onReturn(result);
    }


    @RPC(id = 7)
    public UserMessage onLeave(){
        Long uid = getClientId();
        if(uid == null){
            logger.debug("uid not bind with {}", getContext().getChannel().remote());
            return null;
        }

        Player player = playerManager.getPlayer(uid);
        if(player != null){
            Room room = player.getRoom();
            if(room != null){
                room.leave(uid);
            }else{
                logger.debug("room not found for {}", uid);
            }
            roomManager.leave(uid);
        }
        Result<Boolean> result = new Result<>();
        result.setCode(0);
        result.setValue(true);
        return clientManager.msg().onReturn(result);
    }

    @RPC(id = 9, args = {"characterId"})
    public UserMessage onChangeCharacter(Integer characterId){
        Long uid = getClientId();
        User user = userRepository.findOneByUid(uid);
        user.setCharacterId(characterId);
        userRepository.save(user);
        return clientManager.msg().onCode(0);
    }

    @RPC(id = 11)
    public UserMessage onQueryRoomInfo(){
        Long uid = getClientId();
        Player player = playerManager.getPlayer(uid);
        if(player == null){
            logger.warn("player {} not found", uid);
            return clientManager.msg().onCode(-1);
        }

        Room room = player.getRoom();
        if(room != null){
            RoomInfo roomInfo = new RoomInfo();
            roomInfo.setRoomId(room.getId());
            roomInfo.setPlayerA(room.getPlayerA());
            roomInfo.setPlayerB(room.getPlayerB());
            Result<RoomInfo> result = new Result<>();
            result.setCode(0);
            result.setValue(roomInfo);
            return clientManager.msg().onReturn(result);
        }else{
            logger.warn("room not found for {}", uid);
            return clientManager.msg().onCode(-1);
        }

    }

    @RPC(id = 13, args = {"result"})
    public UserMessage onCommitResult(String result){
        Long uid = getClientId();
        Player player = playerManager.getPlayer(uid);
        if(player == null){
            logger.error("unbind from {}", getContext().getChannel().remote());
            return clientManager.msg().onCode(-1);
        }else{
            Room room = player.getRoom();
            if(room != null){
                logger.debug("room player size:{}", room.size());
                room.leave(uid);
                try{
                    if(room.size() == 0) {
                        GameProto.PKG_VERIFY verifyPkg = room.buildVerify();
                        ByteBuffer buffer = ByteBuffer.wrap(verifyPkg.toByteArray());
                        String verifyResult = verifyClient.getClient().calc(buffer);
                        logger.debug("verifyResult:{}", verifyResult);
                    }
                }catch (Throwable t){
                    logger.error("exception", t);
                }
            }else{
                logger.debug("room not found for {}", uid);
            }
            return clientManager.msg().onCode(0);
        }
    }

    List<Player> waitingList = new LinkedList<>();
    @RPC(id = 5)
    public synchronized UserMessage onJoinRoom(){
        Long uid = getClientId();
        logger.debug("player {} try to join", uid);
        for(Player p : waitingList){
            if(p.getUserId() == uid){
                logger.debug("{} already join", uid);
                return null;
            }
        }

        Result<RoomInfo> result = new Result<>();
        Player player = playerManager.getPlayer(uid);
        waitingList.add(player);
        logger.debug("waiting list:{}", waitingList);

        if(waitingList.size()>=2){
            //create room
            Player playerA = waitingList.remove(0);
            logger.debug("get 1UP:{}", playerA.getUserId());
            Player playerB = waitingList.remove(0);
            logger.debug("get 2UP:{}", playerB.getUserId());
            Room room = roomManager.createRoom(playerA.getUserId(), playerB.getUserId());
            room.join(playerA);
            room.join(playerB);
            playerA.setRoom(room);
            playerB.setRoom(room);

            RoomInfo roomInfo = new RoomInfo();
            roomInfo.setRoomId(room.getId());
            roomInfo.setPlayerA(playerA.getUserId());
            roomInfo.setPlayerB(playerB.getUserId());
            result.setCode(0);
            result.setValue(roomInfo);
            UserMessage userMessage = clientManager.msg().onReturn(result);
            userMessage.setMsgID((short) 6);
            playerA.send(userMessage);
            playerB.send(userMessage);
        }else{
            logger.debug("{} waiting another player", uid);
        }
        return null;
    }

    public User createNewUser(String guid) {
        User user = null;
        user = new User();
        user.setGuid(guid);

        userRepository.save(user);
        //userRepository.insert(user.getGuid(),0);

        user = userRepository.findOneByGuid(guid);

        return user;
    }

    public void saveUser(User user) {
        try {
            userRepository.save(user);
        } catch (Throwable t) {
            logger.error("exception", t);
        }
    }
}
